package com.hontech.bread.utils

import com.hontech.bread.R
import com.hontech.bread.device.mdb.Bill
import com.hontech.bread.device.mdb.BillAccept
import com.hontech.bread.device.mdb.Coin
import com.hontech.bread.device.mdb.CoinDeposited
import com.hontech.bread.device.mdb.Pos
import com.hontech.bread.device.mdb.PosSessionBegin
import com.hontech.bread.store.writeError
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.TimeoutCancellationException
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.withTimeout
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode

class PayCancelException(msg: String) : Exception(msg)

class OrderPayInfo(
    val value: Int,
    val coin: Int,
    val bill: Int,
    val pos: Int,
    val change: Int,
) {


    override fun toString(): String {
        return "$value COIN:${coin} BILL:${bill} POS:${pos} CHANGE:${change}"
    }
}

class PayManager(val value: Int, val change: (Int) -> Unit, val recv: (PayInfo) -> Unit) {

    companion object {

        const val PAY_UNKNOWN = -1
        const val PAY_COIN = 0
        const val PAY_BILL = 1
        const val PAY_POS = 2
        const val PAY_CANCEL = 3

        const val METHOD_COIN = "COIN"
        const val METHOD_BILL = "BILL"
        const val METHOD_POS = "POS"

        const val METHOD_COIN_BILL = "$METHOD_COIN $METHOD_BILL"
        const val METHOD_UNKNOWN = "UNKNOWN"
    }

    class PayInfo(val value: Int, val method: Int) {

        override fun toString(): String {
            return when (method) {
                PAY_UNKNOWN -> "${stringById(R.string.t1733460848_459)}"
                PAY_COIN -> "${stringById(R.string.t1733460848_460)}"
                PAY_BILL -> "${stringById(R.string.t1733460848_461)}"
                PAY_POS -> "${stringById(R.string.t1733460848_462)}"
                PAY_CANCEL -> "${stringById(R.string.t1733460848_463)}"
                else -> "Unknown"
            }
        }

        fun info(): String {
            return when (method) {
                PAY_UNKNOWN -> METHOD_UNKNOWN
                PAY_COIN -> METHOD_COIN
                PAY_BILL -> METHOD_BILL
                PAY_POS -> METHOD_POS
                PAY_CANCEL -> "Cancel"
                else -> "Unknown2"
            }
        }
    }

    private val payChan = Channel<PayInfo>(5)
    private val billRecvFut = CompletableDeferred<Unit>()

    private var coinValue = 0
    private var billValue = 0
    private var posValue = 0

    private var cancelFlag = false
    private var changeValue = 0


    fun getOrderPayInfo(): OrderPayInfo {
        return OrderPayInfo(value, coinValue, billValue, posValue, changeValue)
    }

    suspend fun waitPay() {
        if (value == 0) {
            return
        }

        registerEvent(this)
        try {
            waitPayImpl()
        } catch (e: PayCancelException) {
            refundAll()
            throw e
        } finally {
            unregister(this)
        }
    }

    private suspend fun disableAll() {
        execIgnore { Coin.disable() }
        execIgnore { Bill.disable() }
        execIgnore { Pos.disable() }
    }

    private suspend fun refundAll() {

        if (changeValue == 0) {
            if (coinValue != 0) {
                execIgnore {
                    Coin.change(coinValue)
                    coinValue = 0
                }
            }

            if (billValue != 0) {
                execIgnore {
                    Bill.escrow(0)
                    billValue = 0
                }
            }

            if (posValue != 0) {
                execIgnore { Pos.vendFail() }
            }
        } else {

            val refundValue = billValue + coinValue - changeValue
            if (refundValue >= billValue) {
                if (billValue > 0) {
                    execIgnore { Bill.escrow(0) }
                }
                if (refundValue > billValue) {
                    execIgnore { Coin.change(refundValue - billValue) }
                }
            } else {
                if (billValue > 0) {
                    execIgnore { Bill.escrow(1) }
                }
                execIgnore { Coin.change(refundValue) }
            }
        }

        disableAll()
    }

    private suspend fun waitOncePay(): PayInfo {

        val info = try {
            withTimeout(60 * 1000) { payChan.receive() }
        } catch (e: TimeoutCancellationException) {
            throw PayCancelException("${stringById(R.string.t1733460848_464)}")
        }
        if (info.method == PAY_CANCEL) {
            throw PayCancelException("${stringById(R.string.t1733460848_465)}")
        } else if (info.method == PAY_UNKNOWN) {
            throw IllegalStateException("${stringById(R.string.t1733460848_466)}")
        }
        return info
    }

    private suspend fun payInit() {
        var count = 0

        try {
            Coin.onceReset()
        } catch (e: Exception) {
            e.printStackTrace()
            writeError(e)
            count += 1
        }

        try {
            Bill.onceReset()
        } catch (e: Exception) {
            e.printStackTrace()
            writeError(e)
            count += 1
        }

        try {
            Pos.onceReset()
        } catch (e: Exception) {
            e.printStackTrace()
            writeError(e)
            count += 1
        }

        if (count >= 3) {
            throw IllegalStateException("${stringById(R.string.t1733460848_467)}")
        }
    }

    private suspend fun waitPayImpl() {
        payInit()

        while ((coinValue + billValue + posValue) < value) {

            val payInfo = waitOncePay()
            when (payInfo.method) {
                PAY_COIN -> coinValue += payInfo.value
                PAY_BILL -> billValue += payInfo.value
                PAY_POS -> requestPos()
            }
            recv(payInfo)
        }

        if (cancelFlag) {
            throw PayCancelException("${stringById(R.string.t1733460848_468)}2")
        }

        if ((coinValue + billValue + posValue) > value) {
            val n = coinValue + billValue + posValue - value
            try {
                Coin.enable()
                change(n)
                Coin.change(n)
            } catch (e: Exception) {
                e.printStackTrace()
                throw PayCancelException("${stringById(R.string.t1733460848_469)} ${stringById(R.string.t1733460848_470)}")
            }
            changeValue = n
        }
    }

    private suspend fun requestPos() {

        execIgnore { Coin.disable() }

        if (billValue == 0) {
            execIgnore { Bill.disable() }
        }

        val n = value - coinValue - billValue
        execIgnore {
            Pos.vendRequest(n, 30 * 1000)
            posValue = n
        }
    }


    suspend fun cancel() {
        if (cancelFlag) {
            return
        }
        cancelFlag = true
        payChan.send(PayInfo(0, PAY_CANCEL))
    }

    suspend fun success() {
        if (value == 0) {
            return
        }

        if (posValue != 0) {
            execIgnore { Pos.vendSuccess() }
        }
        if (billValue != 0) {
            execIgnore {
                Bill.escrow(1)
                billRecvFut.await(10 * 1000, stringById(R.string.t1733460848_471))
            }
        }
        disableAll()
    }

    suspend fun fail() {
        if (value == 0) {
            return
        }
        refundAll()
    }

    @Subscribe(threadMode = ThreadMode.MAIN_ORDERED)
    fun onCoinPay(coinDeposited: CoinDeposited) {
        payChan.trySend(PayInfo(coinDeposited.value(), PAY_COIN))
    }

    @Subscribe(threadMode = ThreadMode.MAIN_ORDERED)
    fun onBillPay(billAccept: BillAccept) {
        if (billAccept.isAccept()) {
            payChan.trySend(PayInfo(billAccept.value(), PAY_BILL))
        } else if (billAccept.isRecv()) {
            billRecvFut.complete(Unit)
        }
    }

    @Subscribe(threadMode = ThreadMode.MAIN_ORDERED)
    fun onPosPay(session: PosSessionBegin) {
        payChan.trySend(PayInfo(0, PAY_POS))
    }
}



